
using Boilen.Primitives.CodeGeneration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Xunit;


namespace Boilen.Primitives.Implementers {

    // Contains utility methods to help test Implementers.
    public abstract class TestImplementers {

        static TestImplementers( ) {
            GlobalSettings.ValidationSubNamespace = "Guards";
            GlobalSettings.FreezableSubNamespace = "Freezables";
            GlobalSettings.DependencyPropertySubNamespace = "Validations";
        }

        protected static IEnumerable<object[]> SourceTypesCore {
            get {
                yield return new object[] { typeof( SourceClass ) };
                yield return new object[] { typeof( SourceStruct ) };
            }
        }


        private static T[] GetMembers<T>( IEnumerable<MemberInfo> members )
            where T : MemberInfo {
            return members.OfType<T>( ).ToArray( );
        }

        private static T[] CheckMemberCount<T>( IEnumerable<MemberInfo> members, int expectedCount )
            where T : MemberInfo {
            T[] desiredMembers = GetMembers<T>( members );
            Assert.Equal( desiredMembers.Length, expectedCount );
            return desiredMembers;
        }


        protected static PropertyInfo[] CheckProperties( IEnumerable<MemberInfo> members, MemberTestInfo[] propInfos, Action<PropertyInfo> check ) {
            Assert.True( propInfos.Length > 0, "No properties specified." );

            var properties = CheckMemberCount<PropertyInfo>( members, propInfos.Length );
            foreach( var propInfo in propInfos ) {
                var property = properties.Single( p => p.Name == propInfo.MemberName );
                Assert.Equal( property.PropertyType, propInfo.Type );
                check( property );
            }

            return properties;
        }

        protected static void CheckParameters( ParameterInfo[] parameters, MemberTestInfo[] propInfos ) {
            var joinedParameters = parameters.Join(
                propInfos,
                param => param.Name, prop => prop.ParameterName,
                ( param, prop ) => new { Parameter = param, PropertyInfo = prop }
            ).ToArray( );
            Assert.Equal( joinedParameters.Length, propInfos.Length );

            foreach( var item in joinedParameters ) {
                var parameter = item.Parameter;
                var propInfo = item.PropertyInfo;

                Assert.Equal( parameter.ParameterType, propInfo.Type );
            }
        }

        protected static ConstructorInfo[] CheckConstructors( IEnumerable<MemberInfo> members, int expectedConstructorCount, params MemberTestInfo[] propInfos ) {
            Assert.True( propInfos.Length > 0, "No properties specified." );

            //TODO: move any other common requirements here
            var constructors = CheckMemberCount<ConstructorInfo>( members, expectedConstructorCount )
                .OrderByDescending( c => c.GetParameters( ).Length )
                .ToArray( );

            return constructors;
        }

        protected static IDisposable ChangeExternalDocumentationPrefix( string prefix = "prefix/" ) {
            return new ResetExternalDocumentationPrefix( prefix );
        }


        protected class MemberTestInfo {
            public readonly string ParameterName;
            public readonly string MemberName;
            public readonly string Description;
            public readonly Type Type;
            public virtual bool HasDefaultValue { get { return false; } }

            public MemberTestInfo( string name, Type type ) {
                this.ParameterName = name;
                this.MemberName = Util.Capitalize( name );
                this.Description = "test-description-for-" + name;
                this.Type = type;
            }

            public override string ToString( ) { return this.Type.Name + " " + this.ParameterName; }


            public class DefaultValuePropertyTestInfo<T> : MemberTestInfo {
                public readonly T DefaultValue;
                public override bool HasDefaultValue { get { return true; } }

                public DefaultValuePropertyTestInfo( string name, Type type, T defaultValue )
                    : base( name, type ) {
                    this.DefaultValue = defaultValue;
                }

                public override string ToString( ) { return base.ToString( ) + " = " + this.DefaultValue; }
            }


            public static MemberTestInfo Create<T>( string name ) { return new MemberTestInfo( name, typeof( T ) ); }
            public static DefaultValuePropertyTestInfo<T> Create<T>( string name, T defaultValue ) { return new DefaultValuePropertyTestInfo<T>( name, typeof( T ), defaultValue ); }
        }

        private class ResetExternalDocumentationPrefix : IDisposable {
            private readonly string original_;

            public ResetExternalDocumentationPrefix( string prefix ) {
                this.original_ = GlobalSettings.ExternalDocumentationPrefix;
                GlobalSettings.ExternalDocumentationPrefix = prefix;
            }

            public void Dispose( ) {
                GlobalSettings.ExternalDocumentationPrefix = this.original_;
            }
        }


    }

}
